package com.satyam.smooth.dao.hibernate;

import com.satyam.smooth.dao.exception.DAOException;
import com.satyam.smooth.dao.generic.GenericDAO;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;

public abstract class GenericHibernateDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private final Class<T> clazz;
    private Session session;

    @SuppressWarnings("unchecked")
    public GenericHibernateDAO(Class<T> clazz, GenericSessionProvider sessionProvider) {
        this.clazz = clazz;
        this.session = sessionProvider.getSession();
    }

    public Session getSession() {
        if (session == null) {
            throw new IllegalStateException(
                    "Session has not been set on DAO before usage");
        }
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public Class<T> getPersistentClass() {
        return clazz;
    }

    @Override
    public T findById(ID id) {
        return findById(id, true);
    }

    @Override
    public T findById(ID id, boolean lock) {
        T entity;
        if (lock) {
            entity = (T) getSession().load(getPersistentClass(), id,
                    LockMode.UPGRADE);
        } else {
            entity = (T) getSession().load(getPersistentClass(), id);
        }

        return entity;
    }

    @Override
    public List<T> findAll() {
        return findByCriteria();
    }

    @Override
    public List<T> findByExample(T exampleInstance) {
        return findByExample(exampleInstance, null);
    }

    @Override
    public List<T> findByExample(T exampleInstance, String[] excludeProperty) {
        if (exampleInstance == null) {
            throw new IllegalArgumentException("exampleInstance = null in findbyExample " + clazz.getName());
        }

        try {
            Criteria crit = getSession().createCriteria(getPersistentClass());
            Example example = Example.create(exampleInstance);
            if (excludeProperty != null) {
                for (String exclude : excludeProperty) {
                    example.excludeProperty(exclude);
                }
            }
            crit.add(example);
            return crit.list();
        } catch (HibernateException hibernateEx) {
            throw new DAOException("findByExample() failed in class = " + clazz.getName(), hibernateEx);
        }

    }

    @Override
    public void saveOrUpdate(T entity) {
        if (entity == null) {
            throw new IllegalArgumentException("entity = null in saveOrUpdate method. in class " + clazz.getSimpleName());
        }
        try {
            getSession().saveOrUpdate(entity);
        } catch (HibernateException hibernatEx) {
            throw new DAOException("saveOrUpdate() failed in class = " + clazz.getSimpleName(), hibernatEx);
        }
    }

    @Override
    public void deleteById(ID id) {
        if (id == null) {
            throw new IllegalArgumentException("entity = null in deleteById method. in class " + clazz.getSimpleName());
        }
        T t = findById(id);
        getSession().delete(t);
    }

    @Override
    public void delete(T entity) {
        getSession().delete(entity);
    }

    public void flush() {
        getSession().flush();
    }

    public void clear() {
        getSession().clear();
    }

    /**
     * Use this inside subclasses as a convenience method.
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Criterion... criterion) {
        Criteria crit = getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        return crit.list();
    }

    @Override
    public boolean exists(T entity) {
        boolean existence = findByExample(entity).size() > 0;
        return existence;
    }

}
